package com.portal.common.security.aspect;

import com.portal.common.core.exception.ServiceException;
import com.portal.common.core.text.Convert;
import com.portal.common.redis.service.RedisService;
import com.portal.common.security.annotation.ApiLimit;
import com.portal.common.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 限流思路：
 * <p>
 * 通过API路径作为redis的key，访问次数作为value的方式对某一个用户的某一个请求进行访问唯一标志
 * 每次访问时判断key是否存在，是否超过了限制次数
 * </p>
 */
@Slf4j
@Aspect
@Component
public class LimitAspect {
    private static final String LIMIT_MSG = "请求次数过多，请稍后再试...";

    @Resource
    private RedisService redisService;

    /**
     * 定义AOP签名 (切入所有使用鉴权注解的方法)
     */
    public static final String POINTCUT_SIGN = "@annotation(com.portal.common.security.annotation.ApiLimit)";

    /**
     * 声明AOP签名
     */
    @Pointcut(POINTCUT_SIGN)
    void pointcut() {
        // do nothing
    }

    /**
     * 环绕切入
     * @param joinPoint 切面对象
     * @return 底层方法执行后的返回值
     * @throws Throwable 底层方法抛出的异常
     */
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取当前用户ID
        Long userId = SecurityUtils.getUserId();
        if (userId == null) {
            log.warn("User ID is null, skipping rate limiting");
            throw new ServiceException(LIMIT_MSG);
        }

        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            log.warn("Request attributes are null, skipping rate limiting");
            throw new ServiceException(LIMIT_MSG);
        }

        HttpServletRequest request = attributes.getRequest();
        String requestURI = request.getRequestURI();
        String remoteHost = request.getRemoteHost();

        // 生成唯一键
        String uniqueKey = "api:limit:" + requestURI + ":" + userId;

        // 注解鉴权
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        ApiLimit apiLimitAnnotation = method.getAnnotation(ApiLimit.class);

        int frequency = apiLimitAnnotation.frequency();
        int period = apiLimitAnnotation.period(); // 获取时间窗口

        // 增加访问次数
        Long count = redisService.incrementCacheObject(uniqueKey, 1);
        // 设置过期时间为period秒
        redisService.expire(uniqueKey, period);

        // 记录日志
        log.info("IP:{} 第 {} 次访问===> [{}] <===的接口", remoteHost, count, requestURI);

        // 检查访问次数是否超过限制
        if (count > frequency) {
            throw new ServiceException(LIMIT_MSG);
        }
        return joinPoint.proceed();
    }
}
